/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.virtual.model.startup;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import offset.nodes.Constants;
import offset.nodes.server.model.RepositoryStartupExtension;

/**
 *
 * @author Walter Luetgenau
 */
public class CreationModificationStampStartupExtension implements RepositoryStartupExtension {

    Session systemSession;
    static final String JCR_TIMESTAMP = "yyyy-MM-dd'T'HH:mm:ss.SSSz";

    /**
     * Register an event listener for creation and modification stamps.
     * Called by the RepositoryExtensionsManager on startup.
     * 
     * @param systemSession
     */
    public void onStartup(Session systemSession) {
        try {
            this.systemSession = systemSession;
            
            // creation timestamp
            systemSession.getWorkspace().getObservationManager().addEventListener(
                    new StampObserver(Constants.TYPENAME_CREATION, Constants.PROP_CREATION_USER_ID, Constants.PROP_CREATION_TIMESTAMP), 
                    Event.NODE_ADDED,
                    "/",
                    true, 
                    null, 
                    null, 
                    true);
            
            // modification timestamp
            systemSession.getWorkspace().getObservationManager().addEventListener(
            new StampObserver(Constants.TYPENAME_MODIFICATION, Constants.PROP_MODIFICATION_USER_ID, Constants.PROP_MODIFICATION_TIMESTAMP),
                    Event.NODE_ADDED + Event.NODE_REMOVED + Event.PROPERTY_ADDED + Event.PROPERTY_CHANGED + Event.PROPERTY_REMOVED,
                    "/",
                    true,
                    null,
                    new String[]{Constants.TYPENAME_MODIFICATION},
                    true);
        } catch (RepositoryException ex) {
            Logger.getLogger(CreationModificationStampStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Return the current time, format as JCR time.
     * @return the formatted time
     */
    protected String currentTime() {
        SimpleDateFormat formatter = new SimpleDateFormat(JCR_TIMESTAMP);
        return formatter.format(new Date());
    }

    /**
     * Stores a timestamp and a userid to the node, that has been modified. If this is a creation node, this will be done only during node creation,
     * else on every modification, depending on the registered event type.
     */
    class StampObserver implements EventListener {

        String nodeType = null;
        String userIdName;
        String timestampName;

        /**
         * Add a userId and a current timestamp to nodes of certain type. If the nodeType name is provided, check, if this event node is the correct
         * node type.
         * 
         * @param nodeType If not null, check for this type
         * @param userIdName The user id property name of the nodeType
         * @param timestampName The timestamp property name of the nodeType
         */
        public StampObserver(String nodeType, String userIdName, String timestampName) {
            this.nodeType = nodeType;
            this.userIdName = userIdName;
            this.timestampName = timestampName;
        }

        public void onEvent(EventIterator events) {
            HashSet<String> processedPathes = new HashSet<String>();
            try {
                Node root = systemSession.getRootNode();

                while (events.hasNext()) {
                    Event event = events.nextEvent();

                    String path = event.getPath().substring(1);
                    if (event.getType() == Event.PROPERTY_ADDED || event.getType() == Event.PROPERTY_CHANGED || event.getType() == Event.PROPERTY_REMOVED)
                        path = path.substring(0, path.lastIndexOf("/"));

                    if (processedPathes.contains(path))
                        continue;
                    else
                        processedPathes.add(path);

                    if (!root.hasNode(path))
                        continue;

                    Node node = root.getNode(path);
                    if (nodeType != null && !node.isNodeType(nodeType))
                        continue;

                    node.refresh(false);
                    if (!node.isCheckedOut())
                        continue;

                    node.setProperty(userIdName, event.getUserID());
                    node.setProperty(timestampName, Calendar.getInstance());


                    processedPathes.add(event.getPath());
                }

                systemSession.save();
            } catch (Throwable ex) {
                Logger.getLogger(CreationModificationStampStartupExtension.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
